#include "libAcoustX/solver/bhp/BHPSolver.h"
#include "libAcoustX/solver/bhp/BHPCaller.h"
#include "libGeoutils/tool/datetime_tool.h"
#include "Poco/Exception.h"
#include "Poco/Runnable.h"
#include "Poco/ThreadPool.h"

class BhpCallerRunnable : public ax::bhp::BhpCaller, public Poco::Runnable
{
public:
    double mBearingDeg;
    ax::res::Line::sptr mProfileRes;
    void run() override 
    {
        mProfileRes = Call();
    }
};

ax::bhp::Solver::Solver()
{
}

ax::bhp::Solver::~Solver()
{
}

void ax::bhp::Solver::setBellhopExePath(const std::string& path)
{
    mBellhopExeFullPath = path;
}

std::string ax::bhp::Solver::getBellhopExePath() const
{
    return mBellhopExeFullPath;
}

ax::res::Line::sptr ax::bhp::Solver::solveLine(ax::param::Line::sptr solve_param)
{
    ax::res::Line::sptr result;

    if (!solve_param) {
        mLastErrorMessage = "AxError: input solve_param nullptr";
        return result;
    }
    if (!getSource()) {
        mLastErrorMessage = "AxError: input source nullptr";
        return result;
    }
    if (!getOceanDataset()) {
        mLastErrorMessage = "AxError: input ocean data nullptr";
        return result;
    }

    ax::bhp::BhpCaller caller;
    caller.setBhpExePath(mBellhopExeFullPath);
    caller.setOceanData(getOceanDataset());
    caller.setParam(solve_param);
    caller.setSource(getSource());

    result = caller.Call();
    mLastErrorMessage = caller.getLastErrorMessage();
    return result;
}

ax::res::Point::sptr ax::bhp::Solver::solvePoint(param::Point::sptr solve_param)
{
    ax::res::Point::sptr result;

    if (!solve_param) {
        mLastErrorMessage = "AxError: input solve_param nullptr";
        return result;
    }
    if (!getSource()) {
        mLastErrorMessage = "AxError: input source nullptr";
        return result;
    }
    if (!getOceanDataset()) {
        mLastErrorMessage = "AxError: input ocean data nullptr";
        return result;
    }

	auto curr_time_dir_str = geo::tool::DateTime::now()->toString();
    auto lineParams = solve_param->toLineParams(getSource());

    if (solve_param->getOptIsUseMutiThreads()) 
    {
        Poco::ThreadPool pool(lineParams.size(), lineParams.size());
        std::vector<std::shared_ptr<BhpCallerRunnable>> tasks;

        for (auto& loop_profile_point : lineParams)
        {
			loop_profile_point.second->setOptSolveID(curr_time_dir_str);
            loop_profile_point.second->setOptSolveNumber(loop_profile_point.first);

            auto loop_task = std::make_shared<BhpCallerRunnable>();
			loop_task->mBearingDeg = loop_profile_point.first;
			loop_task->setBhpExePath(mBellhopExeFullPath);
			loop_task->setOceanData(getOceanDataset());
			loop_task->setParam(loop_profile_point.second);
			loop_task->setSource(getSource());
			tasks.push_back(loop_task);

            bool isCommit = false;
            while (!isCommit) {
                try {
                    pool.start(*loop_task.get());
                    isCommit = true;
                } catch (const Poco::NoThreadAvailableException& e) {
                    isCommit = false;
                } catch (...) {
                    isCommit = true;
                    break;
                }
            }
        }

        pool.joinAll();

        result = std::make_shared<ax::res::Point>();
        for (const auto& loop_task : tasks) {
            result->appendBearingResult(loop_task->mBearingDeg, loop_task->mProfileRes);
        }
        return result;
    }
    else 
    {
        result = std::make_shared<ax::res::Point>();

        for (auto& loop_profile_point : lineParams)
        {
            loop_profile_point.second->setOptSolveID(curr_time_dir_str);
            loop_profile_point.second->setOptSolveNumber(loop_profile_point.first);

            auto loop_task = std::make_shared<BhpCallerRunnable>();
            loop_task->mBearingDeg = loop_profile_point.first;
            loop_task->setBhpExePath(mBellhopExeFullPath);
            loop_task->setOceanData(getOceanDataset());
            loop_task->setParam(loop_profile_point.second);
            loop_task->setSource(getSource());
            loop_task->Call();

            result->appendBearingResult(loop_task->mBearingDeg, loop_task->mProfileRes);
        }

        return result;
    }

    return result;
}

ax::res::Area::sptr ax::bhp::Solver::solveArea(param::Area::sptr solve_param)
{
    return res::Area::sptr();
}
